use super::element::{Card, CardType};
use std::collections::HashMap;

type Power = u8;

pub trait Algorithm {
    fn hu_list(&self, hand_cards: Vec<Card>) -> HashMap<Card, Power>;
    fn gang_list(&self, hand_cards: Vec<Card>) -> Vec<Card>;
    fn check_pen(&self, hand_cards: Vec<Card>, card: Card) -> bool;
    fn check_gang(&self, hand_cards: Vec<Card>, card: Card) -> bool;
    fn check_hu(&self, hand_cards: Vec<Card>, card: Card) -> Option<Power>;
}

struct State {
    cards_count: i8,
    cards: HashMap<Card, i8>,
    result: HashMap<Card, Power>,
}

impl State {
    fn new() -> Self {
        State {
            cards_count: 0,
            cards: HashMap::new(),
            result: HashMap::new(),
        }
    }
}

struct DefaultAlgorithm {}

impl DefaultAlgorithm {
    fn new() -> Self {
        DefaultAlgorithm {}
    }

    fn add_count(&self, state: &mut State, card: Card, delta: i8) {
        state.cards_count += delta;
        let count = state.cards.entry(card).or_insert(0);
        *count += delta;
        if *count <= 0 {
            state.cards.remove(&card);
        }
    }

    fn search_hu_list(&self, state: &mut State, has_jiang: bool) {
        // termination
        {
            let total = state.cards_count;

            let mut add_if_not_none = |c: Card, p: Power| {
                if c.tp() != CardType::None {
                    state.result.entry(c).or_insert(p);
                }
            };

            match total {
                2 => {
                    match state.cards.iter().next() {
                        Some((card, 2)) => {
                            state.result.entry(*card).or_insert(1);
                        }
                        Some((card, 1)) => {
                            if state.cards.contains_key(&card.next_n(1)) {
                                add_if_not_none(card.next_n(2), 1);
                                add_if_not_none(card.next_n(-1), 1);
                            } else if state.cards.contains_key(&card.next_n(2)) {
                                add_if_not_none(card.next_n(1), 1);
                            } else if state.cards.contains_key(&card.next_n(-1)) {
                                add_if_not_none(card.next_n(-2), 1);
                                add_if_not_none(card.next_n(1), 1);
                            } else if state.cards.contains_key(&card.next_n(-2)) {
                                add_if_not_none(card.next_n(-1), 1);
                            }
                        }
                        _ => ()
                    }
                    return;
                }
                1 => {
                    if let Some(c) = state.cards.keys().next() {
                        state.result.entry(*c).or_insert(1);
                    }
                    return;
                }
                _ => (),
            }
        }

        // recursion
        // expensive costs
        state.cards.to_owned().keys().for_each(|card| {
            let card = *card;

            //ABC
            let next = card.next();
            let next_next = next.next();
            if state.cards.contains_key(&next) && state.cards.contains_key(&next_next) {
                self.add_count(state, card, -1);
                self.add_count(state, next, -1);
                self.add_count(state, next_next, -1);
                self.search_hu_list(state, has_jiang);
                self.add_count(state, card, 1);
                self.add_count(state, next, 1);
                self.add_count(state, next_next, 1);
            }

            //AAA
            if let Some(c) = state.cards.get(&card) {
                if *c >= 3 {
                    self.add_count(state, card, -3);
                    self.search_hu_list(state, has_jiang);
                    self.add_count(state, card, 3);
                }
            }

            //DD
            if !has_jiang {
                if let Some(c) = state.cards.get(&card) {
                    if *c >= 2 {
                        self.add_count(state, card, -2);
                        self.search_hu_list(state, true);
                        self.add_count(state, card, 2);
                    }
                }
            }
        });
    }
}

impl Algorithm for DefaultAlgorithm {
    fn hu_list(&self, hand_cards: Vec<Card>) -> HashMap<Card, Power> {
        let mut state = State::new();

        hand_cards
            .iter()
            .for_each(|c| self.add_count(&mut state, *c, 1));

        self.search_hu_list(&mut state, false);

        state.result
    }

    fn gang_list(&self, hand_cards: Vec<Card>) -> Vec<Card> {
        todo!()
    }

    fn check_pen(&self, hand_cards: Vec<Card>, card: Card) -> bool {
        todo!()
    }

    fn check_gang(&self, hand_cards: Vec<Card>, card: Card) -> bool {
        todo!()
    }

    fn check_hu(&self, hand_cards: Vec<Card>, card: Card) -> Option<Power> {
        todo!()
    }
}

#[cfg(test)]
mod test {
    use crate::majhong::element::Card;

    use super::{Algorithm, DefaultAlgorithm};

    #[test]
    fn hu_list_case_1() {
        let alg = DefaultAlgorithm::new();
        let data = vec![Card::tong(2), Card::tong(3), Card::tong(4), Card::tong(5)];

        let result = alg.hu_list(data);
        assert_eq!(result.len(), 2);
        assert_eq!(result.get(&Card::tong(2)), Some(&1));
        assert_eq!(result.get(&Card::tong(5)), Some(&1));
    }

    #[test]
    fn hu_list_case_2() {
        let alg = DefaultAlgorithm::new();
        let data = vec![
            Card::tong(2),
            Card::tong(2),
            Card::tong(2),
            Card::tong(5),
            Card::tong(7),
            Card::tong(8),
            Card::tong(9),
        ];

        let result = alg.hu_list(data);
        assert_eq!(result.len(), 1);
        assert_eq!(result.get(&Card::tong(5)), Some(&1));
    }

    #[test]
    fn hu_list_case_3() {
        let alg = DefaultAlgorithm::new();
        let data = vec![Card::tong(2), Card::tong(2), Card::tong(3), Card::tong(3)];

        let result = alg.hu_list(data);
        assert_eq!(result.len(), 2);
        assert_eq!(result.get(&Card::tong(2)), Some(&1));
        assert_eq!(result.get(&Card::tong(3)), Some(&1));
    }

    #[test]
    fn hu_list_case_4() {
        let alg = DefaultAlgorithm::new();
        let data = vec![
            Card::tong(1),
            Card::tong(1),
            Card::tong(1),
            Card::tong(2),
            Card::tong(3),
            Card::tong(4),
            Card::tong(5),
            Card::tong(6),
            Card::tong(7),
            Card::tong(8),
            Card::tong(9),
            Card::tong(9),
            Card::tong(9),
        ];

        let result = alg.hu_list(data);
        assert_eq!(result.len(), 9);
        assert_eq!(result.get(&Card::tong(1)), Some(&1));
        assert_eq!(result.get(&Card::tong(2)), Some(&1));
        assert_eq!(result.get(&Card::tong(3)), Some(&1));
        assert_eq!(result.get(&Card::tong(4)), Some(&1));
        assert_eq!(result.get(&Card::tong(5)), Some(&1));
        assert_eq!(result.get(&Card::tong(6)), Some(&1));
        assert_eq!(result.get(&Card::tong(7)), Some(&1));
        assert_eq!(result.get(&Card::tong(8)), Some(&1));
        assert_eq!(result.get(&Card::tong(9)), Some(&1));
    }
}
